package com.cg.springSecurity.security;

import com.cg.springSecurity.mapper.MenuMapper;
import com.cg.springSecurity.pojo.Menu;
import com.cg.springSecurity.pojo.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

/**
 * SecurityMetadataSource接口负责提供受保护对象所需要的权限。由于该案例中，受保护对象所需要的权限保存在数据库中，所以可以通过自定义类继承自
 * FilterInvocationSecurityMetadataSource，并重写getAttributes方法来提供受保护对象所需要的权限。
 */
@Component
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Resource
    MenuMapper menuMapper;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 在基于URL地址的权限控制中，受保护对象就是FilterInvocation。
     * @param object 受保护对象
     * @return 受保护对象所需要的权限
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 从受保护对象FilterInvocation中提取出当前请求的URI地址，例如/admin/hello
        String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
        // 查询所有菜单数据(每条数据中都包含访问该条记录所需要的权限)
        List<Menu> allMenu = menuMapper.getAllMenu();

        // 遍历菜单数据，如果当前请求的URL地址和菜单中某一条记录的pattern属性匹配上了(例如/admin/hello匹配上/admin/**)
        // 那么就可以获取当前请求所需要的权限；如果没有匹配上，则返回null。需要注意的是，如果AbstractSecurityInterceptor
        // 中的rejectPublicInvocations属性为false(默认值)时，则表示当getAttributes返回null时，允许访问受保护对象
        for (Menu menu : allMenu) {
            if (antPathMatcher.match(menu.getPattern(), requestURI)) {
                String[] roles = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                return SecurityConfig.createList(roles);
            }
        }

        return null;
    }

    /**
     * 方便在项目启动阶段做校验，如果不需要校验，则直接返回null即可。
     * @return 所有的权限属性
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * 表示当前对象支持处理的受保护对象是FilterInvocation。
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}
